package com.webex.ta.hydra.core;

import com.webex.ta.hydra.core.macro.MacroDefinition;
import com.webex.ta.hydra.core.macro.MacroSerializer;
import com.webex.ta.hydra.core.macro.Macro;
import com.webex.ta.hydra.exceptions.IllegalKeywordException;
import com.webex.ta.hydra.plugin.Plugin;
import com.webex.ta.hydra.plugin.PluginManager;
import com.webex.ta.hydra.util.ClassFinder;
import com.webex.ta.hydra.util.HydraUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-9-28
 * Time: 17:13:44
 */
public class KeywordRegistry {
    private static KeywordRegistry instance;

    private static final Set<String> IGNORED_KEYWORDS = new HashSet<String>();

    static {
        IGNORED_KEYWORDS.add("com.webex.ta.hydra.core.macro.Macro");
    }

    private final Map<String, Class> coreKeywords = new HashMap<String, Class>();
    private final List<Controller> coreControllers = new ArrayList<Controller>();
    private final List<Action> coreActions = new ArrayList<Action>();
    private final List<Processor> coreProcessors = new ArrayList<Processor>();
    private final List<Assertion> coreAssertions = new ArrayList<Assertion>();

    private final Map<String, Class> pluginKeywords = new HashMap<String, Class>();
    private final List<Controller> pluginControllers = new ArrayList<Controller>();
    private final List<Action> pluginActions = new ArrayList<Action>();
    private final List<Processor> pluginProcessors = new ArrayList<Processor>();
    private final List<Assertion> pluginAssertions = new ArrayList<Assertion>();

    // Cache
    Vector<String> actionNames, ctlNames, procNames, keywordNames, assertionNames;


    private final Map<String, Vector<MacroDefinition>> macros = new HashMap<String, Vector<MacroDefinition>>();

    private boolean dirty = false;
    private boolean macroDirty = false;

    private KeywordRegistry() {
    }

    public static KeywordRegistry getInstance() {
        if (instance == null) {
            instance = new KeywordRegistry();
            instance.loadCoreKeywords();
        }
        return instance;
    }

    public void loadCoreKeywords() {
        coreKeywords.clear();
        coreControllers.clear();
        coreActions.clear();
        coreProcessors.clear();
        coreAssertions.clear();
        try {
            List<String> keywordClasses = ClassFinder.findClassesThatExtend(HydraUtils.getCorePaths(), new Class[]{
                    Keyword.class});
            for (String kwClassName : keywordClasses) {
                if (IGNORED_KEYWORDS.contains(kwClassName))
                    continue;

                Class c = Class.forName(kwClassName);
                Keyword keyword = (Keyword) c.newInstance();
                if (!Modifier.isAbstract(c.getModifiers())) {
                    coreKeywords.put(keyword.getName(), c);
                    distributeCoreKeyword(keyword);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        dirty = true;
    }

    public void loadPluginKeywords() {
        pluginKeywords.clear();
        pluginControllers.clear();
        pluginActions.clear();
        pluginProcessors.clear();
        try {
            Plugin[] plugins = PluginManager.getInstance().getActivatedPlugins();
            String[] paths = new String[plugins.length];
            for (int i = 0; i < plugins.length; i ++) {
                paths[i] = new File(plugins[i].getPluginJar().getPath()).getParent();
            }
            List<String> keywordClasses = ClassFinder.findClassesThatExtend(paths, new Class[]{
                    Keyword.class});
            for (String kwClassName : keywordClasses) {
                if (IGNORED_KEYWORDS.contains(kwClassName))
                    continue;

                Class c = Class.forName(kwClassName);
                Keyword keyword = (Keyword) c.newInstance();
                if (!Modifier.isAbstract(c.getModifiers())) {
                    pluginKeywords.put(keyword.getName(), c);
                    distributePluginKeyword(keyword);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        dirty = true;
    }

    private void distributeCoreKeyword(Keyword keyword) {
        if (keyword instanceof Controller)
            coreControllers.add((Controller) keyword);

        if (keyword instanceof Action)
            coreActions.add((Action) keyword);

        if (keyword instanceof Processor)
            coreProcessors.add((Processor) keyword);

        if (keyword instanceof Assertion)
            coreAssertions.add((Assertion) keyword);
    }

    private void distributePluginKeyword(Keyword keyword) {
        if (keyword instanceof Controller)
            pluginControllers.add((Controller) keyword);

        if (keyword instanceof Action)
            pluginActions.add((Action) keyword);

        if (keyword instanceof Processor)
            pluginProcessors.add((Processor) keyword);

        if (keyword instanceof Assertion)
            pluginAssertions.add((Assertion) keyword);
    }

    private void generateCache() {
        actionNames = new Vector<String>();
        for (Action act : coreActions)
            actionNames.add(act.getName());
        for (Action act: pluginActions)
            actionNames.add(act.getName());
        Collections.sort(actionNames);

        ctlNames = new Vector<String>();
        for (Controller ctl : coreControllers)
            ctlNames.add(ctl.getName());
        for (Controller ctl : pluginControllers)
            ctlNames.add(ctl.getName());
        Collections.sort(ctlNames);

        procNames= new Vector<String>();
        for (Processor proc : coreProcessors)
            procNames.add(proc.getName());
        for (Processor proc : pluginProcessors)
            procNames.add(proc.getName());
        Collections.sort(procNames);

        assertionNames = new Vector<String>();
        for (Assertion assertion : coreAssertions)
            assertionNames.add(assertion.getName());
        for (Assertion assertion : pluginAssertions)
            assertionNames.add(assertion.getName());
        Collections.sort(assertionNames);

        keywordNames = new Vector<String>();
        for (String s : coreKeywords.keySet())
            keywordNames.add(s);
        for (String s : pluginKeywords.keySet())
            keywordNames.add(s);
        Collections.sort(keywordNames);
    }

    public Vector<String> getKeywordNames() {
        dirtyCheck();
        return keywordNames;
    }

    public Vector<String> getKeywordNamesInCategory() {
        Vector<String> keywords = new Vector<String>();
        keywords.addAll(ctlNames);
        keywords.addAll(actionNames);
        keywords.addAll(procNames);
        keywords.addAll(assertionNames);
        keywords.addAll(getMacroNames());
        return keywords;
    }

    public Vector<String> getActionNames() {
        dirtyCheck();
        return actionNames;
    }

    public Vector<String> getControllerNames() {
        dirtyCheck();
        return ctlNames;
    }

    public Vector<String> getProcessorNames() {
        dirtyCheck();
        return procNames;
    }

    public Vector<String> getAssertionNames() {
        dirtyCheck();
        return assertionNames;
    }

    public Vector<String> getMacroNames() {
        Vector<String> names = new Vector<String>();
        for (Vector<MacroDefinition> defs : macros.values()) {
            for (MacroDefinition def : defs) {
                names.add(def.getName());
            }
        }
        Collections.sort(names);
        return names;
    }


    public void unloadMacros(String filePath) {
        try {
            File file = FileService.getInstance().getFile(filePath);
            macros.remove(file.getAbsolutePath());
        } catch (Exception e) {
            // this may produce exception, but we don't care
        }
    }

    public void unloadAllMacros() {
        macros.clear();
        macroDirty = true;
    }

    public void loadMacros(String filePath) {
        try {
            File file = FileService.getInstance().getFile(filePath);
            InputStream istream = new FileInputStream(file);
            Vector<MacroDefinition> defs = MacroSerializer.loadMacroDefinitions(istream);
            macros.put(file.getAbsolutePath(), defs);
        } catch (Exception e) {
            // this may produce exception, but we don't care
        }
        macroDirty = true;
    }

    private void dirtyCheck() {
        if (dirty) {
            generateCache();
            dirty = false;
        }
    }

    public Class getKeywordClass(String keywordName) {
        if (coreKeywords.containsKey(keywordName))
            return coreKeywords.get(keywordName);
        return pluginKeywords.get(keywordName);
    }

    public Keyword createKeyword(String keywordName) throws IllegalKeywordException {
        Class cls = getKeywordClass(keywordName);
        if (cls == null)
            throw new IllegalKeywordException("Keyword not found: " + keywordName);
        try {
            return (Keyword) cls.newInstance();
        } catch (InstantiationException e) {
            throw new IllegalKeywordException("Keyword can't be instantiated: " + keywordName);
        } catch (IllegalAccessException e) {
            throw new IllegalKeywordException("Keyword has a private constructor: " + keywordName);
        }
    }

    public Keyword createKeywordOrMacro(String keywordName) {
        try {
            return createKeyword(keywordName);
        } catch (IllegalKeywordException e) {
            return new Macro(keywordName);
        }
    }

    public ParamDescriptor[] getParamDescriptors(String kwName, Command cmd) throws IllegalKeywordException {
        try {
            // try keyword first
            Keyword kw = createKeyword(kwName);
            return kw.getParamDescriptors(cmd);
        } catch (IllegalKeywordException ignore) {

        }

        MacroDefinition def = getMacroDefinition(kwName);
        if (def == null)
            throw new IllegalKeywordException();
        return def.getFields().toArray(new ParamDescriptor[def.getFields().size()]);
    }

    public MacroDefinition getMacroDefinition(String macroName) {
        for(Vector<MacroDefinition> defs : macros.values()) {
            for (MacroDefinition def : defs) {
                if (def.getName().equals(macroName))
                    return def;
            }
        }
        return null;
    }
}
